package com.xiaozhen.rtmp.server;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaoleilu.hutool.collection.CollectionUtil;
import com.xiaozhen.rtmp.common.MediaConstant;
import com.xiaozhen.rtmp.dao.CameraDao;
import com.xiaozhen.rtmp.domain.Camera;
import com.xiaozhen.rtmp.handler.FlvHandler;
import com.xiaozhen.rtmp.service.HlsService;
import com.xiaozhen.rtmp.service.MediaService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.unix.PreferredDirectByteBufAllocator;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.cors.CorsConfig;
import io.netty.handler.codec.http.cors.CorsConfigBuilder;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.Loader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;

/**
 * HISTORY
 *
 * @author zhenweisheng
 * @date 2021/12/6 16:11
 * @description:
 */
@Component
@Slf4j
public class MediaServer implements CommandLineRunner {

    @Autowired
    private Environment env;
    @Autowired
    private CameraDao cameraDao;
    @Autowired
    private MediaService mediaService;
    @Autowired
    private HlsService hlsService;
    @Value("${mediaserver.port}")
    private int port;
    @Autowired
    private FlvHandler flvHandler;

    @Override
    public void run(String... args) throws Exception {
        this.initAutoPlay();
        String ip = InetAddress.getLocalHost().getHostAddress();
        String httpPort = env.getProperty("server.port");
        String path = env.getProperty("server.servlet.context-path");
        if (StrUtil.isEmpty(path)) {
            path = "";
        }
        log.info("\n--------------------------------------------------------- \n" +
                        "\t EasyMedia is running! Access address: \n" +
                        "\t media port at : \t {} \n" +
                        "\t http port at : \t {} \n" +
                        "\t web Local: \t http://localhost:{} \n" +
                        "\t web External: \t http://{}:{}{} \n" +
                        "\t httpflv: \t http://{}:{}/live?url={您的源地址} \n" +
                        "\t wsflv: \t ws://{}:{}/live?url={您的源地址} \n" +
                        "\t hls(m3u8): \t http://{}:{}/hls?url={您的源地址} \n" +
                        "\t h2-database: \t http://{}:{}/h2-console \n" +
                        "--------------------------------------------------------- \n",
                port,
                httpPort,
                httpPort,
                ip, httpPort, path,
                ip, port,
                ip, port,
                ip, httpPort,
                ip, httpPort);
        this.start(new InetSocketAddress("0.0.0.0", port));
    }

    private void start(InetSocketAddress socketAddress) {
        //new 一个主线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //new 一个工作线程组
        EventLoopGroup workGroup = new NioEventLoopGroup(200);
        ServerBootstrap bootstrap = new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        CorsConfig corsConfig = CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build();

                        socketChannel.pipeline()
                                .addLast(new HttpResponseEncoder())
                                .addLast(new HttpRequestDecoder())
                                .addLast(new ChunkedWriteHandler())
                                .addLast(new HttpObjectAggregator(64 * 1024))
                                .addLast(new CorsHandler(corsConfig))
                                .addLast(flvHandler);
                    }
                })
                .localAddress(socketAddress)
                .option(ChannelOption.SO_BACKLOG, 128)
                //首选直接内存
                .option(ChannelOption.ALLOCATOR, PreferredDirectByteBufAllocator.DEFAULT)
                //设置队列大小
//                .option(ChannelOption.SO_BACKLOG, 1024)
                // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.SO_RCVBUF, 128 * 1024)
                .childOption(ChannelOption.SO_SNDBUF, 1024 * 1024)
                .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024 * 1024 / 2, 1024 * 1024));
        //绑定端口,开始接收进来的连接
        try {
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //关闭主线程组
            bossGroup.shutdownGracefully();
            //关闭工作线程组
            workGroup.shutdownGracefully();
        }
    }


    /**
     * 启动初始化自动拉流（已保存的流，如果已经开启，会自动拉流）
     */
    public void initAutoPlay() {
        List<Camera> selectList = cameraDao.selectList(new QueryWrapper<>());
        if (CollectionUtil.isNotEmpty(selectList)) {
            log.info("已启动自动拉流！");
            for (Camera camera : selectList) {
                if (camera.getFlv()) {
                    mediaService.playForApi(camera);
                }
                if (camera.getHls()) {
                    hlsService.startConvertToHls(camera);
                }
            }

        }

        log.info("您还可以通过restful api添加或删除流！");
    }

    /**
     * 提前初始化，可避免推拉流启动耗时太久
     */
    @PostConstruct
    public void loadFFmpeg() {
        /**
         * 初始化ffmpeg路径
         */
        String ffmpeg = Loader.load(org.bytedeco.ffmpeg.ffmpeg.class);
        System.setProperty(MediaConstant.ffmpegPathKey, ffmpeg);
        log.info("初始化资源成功");
    }
}
